import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
import 'package:thingsboard_app/constants/app_constants.dart';
import 'package:thingsboard_app/core/context/tb_context.dart';
import 'package:thingsboard_app/core/context/tb_context_widget.dart';
import 'package:thingsboard_app/widgets/tb_progress_indicator.dart';
import 'package:thingsboard_app/widgets/two_value_listenable_builder.dart';
import 'package:universal_platform/universal_platform.dart';
import 'package:url_launcher/url_launcher.dart';

class DashboardController {

  final ValueNotifier<bool> canGoBack = ValueNotifier(false);
  final ValueNotifier<bool> hasRightLayout = ValueNotifier(false);
  final ValueNotifier<bool> rightLayoutOpened = ValueNotifier(false);

  final _DashboardState dashboardState;
  DashboardController(this.dashboardState);

  Future<void> openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
    return await dashboardState._openDashboard(dashboardId, state: state, hideToolbar: hideToolbar, fullscreen: fullscreen);
  }

  Future<bool> goBack() async {
    return dashboardState._goBack();
  }

  onHistoryUpdated(Future<bool> canGoBackFuture) async {
    canGoBack.value = await canGoBackFuture;
  }

  onHasRightLayout(bool _hasRightLayout) {
    hasRightLayout.value = _hasRightLayout;
  }

  onRightLayoutOpened(bool _rightLayoutOpened) {
    rightLayoutOpened.value = _rightLayoutOpened;
  }

  Future<void> toggleRightLayout() async {
    await dashboardState._toggleRightLayout();
  }

  Future<void> activateDashboard() async {
    await dashboardState._activateDashboard();
  }

  Future<void> deactivateDashboard() async {
    await dashboardState._deactivateDashboard();
  }

  dispose() {
    canGoBack.dispose();
    hasRightLayout.dispose();
    rightLayoutOpened.dispose();
  }

}

typedef DashboardTitleCallback = void Function(String title);

typedef DashboardControllerCallback = void Function(DashboardController controller);

class Dashboard extends TbContextWidget {

  final bool? _home;
  final bool _activeByDefault;
  final DashboardTitleCallback? _titleCallback;
  final DashboardControllerCallback? _controllerCallback;

  Dashboard(TbContext tbContext, {Key? key, bool? home, bool activeByDefault = true, DashboardTitleCallback? titleCallback, DashboardControllerCallback? controllerCallback}):
        this._home = home,
        this._activeByDefault = activeByDefault,
        this._titleCallback = titleCallback,
        this._controllerCallback = controllerCallback,
        super(tbContext);

  @override
  _DashboardState createState() => _DashboardState();

}

class _DashboardState extends TbContextState<Dashboard> {

  final Completer<InAppWebViewController> _controller = Completer<InAppWebViewController>();

  bool webViewLoading = true;
  final ValueNotifier<bool> dashboardLoading = ValueNotifier(true);
  final ValueNotifier<bool> dashboardActive = ValueNotifier(true);
  final ValueNotifier<bool> readyState = ValueNotifier(false);

  final GlobalKey webViewKey = GlobalKey();

  late final DashboardController _dashboardController;

  bool _fullscreen = false;

  InAppWebViewGroupOptions options = InAppWebViewGroupOptions(
      crossPlatform: InAppWebViewOptions(
        useShouldOverrideUrlLoading: true,
        mediaPlaybackRequiresUserGesture: false,
        javaScriptEnabled: true,
        cacheEnabled: true,
        supportZoom: false,
        // useOnDownloadStart: true
      ),
      android: AndroidInAppWebViewOptions(
        useHybridComposition: true,
        thirdPartyCookiesEnabled: true
      ),
      ios: IOSInAppWebViewOptions(
        allowsInlineMediaPlayback: true,
      ));

  late Uri _initialUrl;

  @override
  void initState() {
    super.initState();
    dashboardActive.value = widget._activeByDefault;
    _dashboardController = DashboardController(this);
    if (widget._controllerCallback != null) {
      widget._controllerCallback!(_dashboardController);
    }
    tbContext.isAuthenticatedListenable.addListener(_onAuthenticated);
    if (tbContext.isAuthenticated) {
      _onAuthenticated();
    }
  }

  void _onAuthenticated() async {
    if (tbContext.isAuthenticated) {
      if (!readyState.value) {
        _initialUrl = Uri.parse(ThingsboardAppConstants.thingsBoardApiEndpoint + '?accessToken=${tbClient.getJwtToken()!}&refreshToken=${tbClient.getRefreshToken()!}');
        readyState.value = true;
      } else {
        var windowMessage = <String, dynamic>{
          'type': 'reloadUserMessage',
          'data': <String, dynamic>{
            'accessToken': tbClient.getJwtToken()!,
            'refreshToken': tbClient.getRefreshToken()!
          }
        };
        if (!UniversalPlatform.isWeb) {
          var controller = await _controller.future;
          await controller.postWebMessage(
              message: WebMessage(data: jsonEncode(windowMessage)),
              targetOrigin: Uri.parse('*'));
        }
      }
    }
  }

  Future<bool> _goBack() async {
    if (!UniversalPlatform.isWeb) {
      if (_dashboardController.rightLayoutOpened.value) {
        await _toggleRightLayout();
        return false;
      }
      var controller = await _controller.future;
      if (await controller.canGoBack()) {
        await controller.goBack();
        return false;
      }
    }
    return true;
  }

  @override
  void dispose() {
    tbContext.isAuthenticatedListenable.removeListener(_onAuthenticated);
    readyState.dispose();
    dashboardLoading.dispose();
    _dashboardController.dispose();
    super.dispose();
  }

  Future<void> _activateDashboard() async {
    if (!dashboardActive.value) {
      dashboardActive.value = true;
    }
  }

  Future<void> _deactivateDashboard() async {
    if (dashboardActive.value) {
      dashboardActive.value = false;
    }
  }

  Future<void> _openDashboard(String dashboardId, {String? state, bool? hideToolbar, bool fullscreen = false}) async {
    _fullscreen = fullscreen;
    dashboardLoading.value = true;
    InAppWebViewController? controller;
    if (!UniversalPlatform.isWeb) {
      controller = await _controller.future;
    }
    var windowMessage = <String, dynamic>{
      'type': 'openDashboardMessage',
      'data': <String, dynamic>{
        'dashboardId': dashboardId
      }
    };
    if (state != null) {
      windowMessage['data']['state'] = state;
    }
    if (widget._home == true) {
      windowMessage['data']['embedded'] = true;
    }
    if (hideToolbar == true) {
      windowMessage['data']['hideToolbar'] = true;
    }
    var webMessage = WebMessage(data: jsonEncode(windowMessage));
    if (!UniversalPlatform.isWeb) {
      await controller!.postWebMessage(
          message: webMessage, targetOrigin: Uri.parse('*'));
    }
  }

  Future<void> _toggleRightLayout() async {
    var controller = await _controller.future;
    var windowMessage = <String, dynamic>{
      'type': 'toggleDashboardLayout'
    };
    var webMessage = WebMessage(data: jsonEncode(windowMessage));
    await controller.postWebMessage(message: webMessage, targetOrigin: Uri.parse('*'));
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
            onWillPop: () async {
              if (widget._home == true && !tbContext.isHomePage()) {
                return true;
              }
              if (readyState.value) {
                return await _goBack();
              } else {
                return true;
              }
            },
            child:
            ValueListenableBuilder(
                valueListenable: readyState,
                builder: (BuildContext context, bool ready, child) {
                  if (!ready) {
                    return SizedBox.shrink();
                  } else {
                    return Stack(
                        children: [
                          UniversalPlatform.isWeb ? Center(child: Text('Not implemented!')) :
                          InAppWebView(
                            key: webViewKey,
                            initialUrlRequest: URLRequest(url: _initialUrl),
                            initialOptions: options,
                            onWebViewCreated: (webViewController) {
                              log.debug("onWebViewCreated");
                              webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLoadedHandler", callback: (args) async {
                                bool hasRightLayout = args[0];
                                bool rightLayoutOpened = args[1];
                                log.debug("Invoked tbMobileDashboardLoadedHandler: hasRightLayout: $hasRightLayout, rightLayoutOpened: $rightLayoutOpened");
                                _dashboardController.onHasRightLayout(hasRightLayout);
                                _dashboardController.onRightLayoutOpened(rightLayoutOpened);
                                dashboardLoading.value = false;
                              });
                              webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardLayoutHandler", callback: (args) async {
                                bool rightLayoutOpened = args[0];
                                log.debug("Invoked tbMobileDashboardLayoutHandler: rightLayoutOpened: $rightLayoutOpened");
                                _dashboardController.onRightLayoutOpened(rightLayoutOpened);
                              });
                              webViewController.addJavaScriptHandler(handlerName: "tbMobileDashboardStateNameHandler", callback: (args) async {
                                log.debug("Invoked tbMobileDashboardStateNameHandler: $args");
                                if (args.isNotEmpty && args[0] is String) {
                                  if (widget._titleCallback != null) {
                                    widget._titleCallback!(args[0]);
                                  }
                                }
                              });
                              webViewController.addJavaScriptHandler(handlerName: "tbMobileNavigationHandler", callback: (args) async {
                                log.debug("Invoked tbMobileNavigationHandler: $args");
                                if (args.length > 0) {
                                  String? path = args[0];
                                  Map<String, dynamic>? params;
                                  if (args.length > 1) {
                                    params = args[1];
                                  }
                                  log.debug("path: $path");
                                  log.debug("params: $params");
                                  if (path != null) {
                                    if ([
                                      'profile',
                                      'devices',
                                      'assets',
                                      'dashboards',
                                      'customers',
                                      'auditLogs'
                                    ].contains(path)) {
                                      var targetPath = '/$path';
                                      if (path == 'devices' && widget._home != true) {
                                        targetPath = '/devicesPage';
                                      }
                                      navigateTo(targetPath);
                                    }
                                  }
                                }
                              });
                              webViewController.addJavaScriptHandler(handlerName: "tbMobileHandler", callback: (args) async {
                                log.debug("Invoked tbMobileHandler: $args");
                                return await widgetActionHandler.handleWidgetMobileAction(args, webViewController);
                              });
                            },
                            shouldOverrideUrlLoading: (controller, navigationAction) async {
                              var uri = navigationAction.request.url!;
                              var uriString = uri.toString();
                              log.debug('shouldOverrideUrlLoading $uriString');
                              if (![
                                "http",
                                "https",
                                "file",
                                "chrome",
                                "data",
                                "javascript",
                                "about"
                              ].contains(uri.scheme)) {
                                if (await canLaunch(uriString)) {
                                  // Launch the App
                                  await launch(
                                    uriString,
                                  );
                                  // and cancel the request
                                  return NavigationActionPolicy.CANCEL;
                                }
                              }

                              return Platform.isIOS ? NavigationActionPolicy.ALLOW : NavigationActionPolicy.CANCEL;
                            },
                            onUpdateVisitedHistory: (controller, url, androidIsReload) async {
                              log.debug('onUpdateVisitedHistory: $url');
                              _dashboardController.onHistoryUpdated(controller.canGoBack());
                            },
                            onConsoleMessage: (controller, consoleMessage) {
                              log.debug('[JavaScript console] ${consoleMessage.messageLevel}: ${consoleMessage.message}');
                            },
                            onLoadStart: (controller, url) async {
                              log.debug('onLoadStart: $url');
                            },
                            onLoadStop: (controller, url) async {
                              log.debug('onLoadStop: $url');
                              if (webViewLoading) {
                                webViewLoading = false;
                                _controller.complete(controller);
                              }
                            },
                            androidOnPermissionRequest: (controller, origin, resources) async {
                              log.debug('androidOnPermissionRequest origin: $origin, resources: $resources');
                              return PermissionRequestResponse(
                                  resources: resources,
                                  action: PermissionRequestResponseAction.GRANT);
                            },
                          ),
                          if (!UniversalPlatform.isWeb)
                            TwoValueListenableBuilder(
                              firstValueListenable: dashboardLoading,
                              secondValueListenable: dashboardActive,
                              builder: (BuildContext context, bool loading, bool active, child) {
                                if (!loading && active) {
                                  return SizedBox.shrink();
                                } else {
                                  var data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
                                  var bottomPadding = data.padding.top;
                                  if (widget._home != true) {
                                    bottomPadding += kToolbarHeight;
                                  }
                                  return Container(
                                    padding: EdgeInsets.only(bottom: bottomPadding),
                                    alignment: Alignment.center,
                                    color: Colors.white,
                                    child: TbProgressIndicator(
                                        size: 50.0
                                    ),
                                  );
                                }
                              }
                          )
                        ]
                    );
                  }
                }
            )
    );
  }
}
